package org.wendu.wdoa.common.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.wendu.wdoa.common.OaConstants;
import org.wendu.wdoa.common.token.TokenUser;
import org.wendu.wdoa.common.token.TokenUtils;
import org.wendu.wdoa.model.User;
import org.wendu.wdoa.common.CurrentUser;
import org.wendu.wdoa.common.Result;
import org.wendu.wdoa.common.config.dao.SecurityDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


@Slf4j
@Configuration
@MapperScan({"org.wendu.wdoa.common.config.dao",
        "org.wendu.wdoa.common.token.dao"})
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private SecurityDao securityDao;


    //配置不加密密码编码器,方法名固定
    @Bean
    public PasswordEncoder passwordEncoder(){

        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString(); //不加密
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.toString().equals(encodedPassword);
            }
        };
    }

//    private UserDetails getUserById(String userId) throws UsernameNotFoundException{
//        User user = securityDao.findUserByUserId(userId);
//
//        CurrentUser currentUser = new CurrentUser(user.getU_id(), user.getU_name(), user.getU_pwd());
//
//        return  currentUser;
//
//
//    }

    private UserDetails getUserById(String userId){

        /*
        如果登录时，userId为账号，
        如果认证（校验token）时userId为令牌，并且不查数据库，返回UserDetails中的userId和密码都为令牌
         */
        if(userId != null && userId.length()>20){ //userID为令牌
            return new CurrentUser(userId,null,userId);
        }

        User user = securityDao.findUserByUserId(userId);
        CurrentUser currentUser = new CurrentUser(user.getU_id(), user.getU_name(), user.getU_pwd());
        return currentUser;

    }

    //配置获取当前用户的业务对象
    @Bean  //方法名必须为userDetailsService
    public UserDetailsService userDetailsService() {

        return this::getUserById;


//           @Override
//           public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//              User user =  securityDao.findUserByUserId(username);
//               CurrentUser currentUser = new CurrentUser(user.getU_id(),user.getU_name(),user.getU_pwd());
//               return currentUser;
//           }
//       };

    }


    //Spring Security 核心安全配置均在这里写
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //1.配置无条件放行的资源和需要登陆后访问的资源
        http.authorizeRequests()
//                .antMatchers("/assets/**").permitAll() //放行
//                .antMatchers("/css/**").permitAll()
//                .antMatchers("/fonts/**").permitAll()
//                .antMatchers("/js/**").permitAll()
//                .antMatchers("/favicon.ico").permitAll()
//                .antMatchers("/toError").permitAll()
//                .antMatchers("/error.html").permitAll()
//                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated(); //需要认证

        //用自定义的过滤器替换指定的过滤器
        http.addFilterAt(customFilter(), UsernamePasswordAuthenticationFilter.class); //替换指定的过滤器
        http.addFilter(new TokenVerifyFilter(authenticationManager())); //将令牌校验过滤器放入过滤器链中



        //登录配置
//        http.formLogin()//配置Spring Security内部登录功能对应的请求路径，不配置默认：/login
//                .loginPage("/login.html") //配置自定义的登录页
//                .loginProcessingUrl("/security/login")
//                .usernameParameter("u_id") //配置登录的参数名称
//                .passwordParameter("u_pwd") //配置登录的密码名称
//                .successHandler( this::onAuthenticationSuccess)//自定义登录成功后怎么办，需要传入一个成功处理器对象
//                .failureHandler( this::onAuthenticationFailure)//自定义认证失败后怎么办，需要传入一个失败后处理器对象
//                .permitAll(); // 放行登陆相关的路径


        //退出配置
        http.logout()
                .logoutUrl("/home/exit")          //配置推出功能的地址
        .logoutSuccessHandler(this::onLogoutSuccess)                       //配置推出成功后的处理器
        .permitAll();                                 //放行退出相关路径

        //自定义异常处理：未认定异常（未登录而访问了需要登陆后访问的地址），非法访问异常（登陆后访问了无权限的地址）
        http.exceptionHandling()
                .authenticationEntryPoint(this::onNotAuthException) //未认定异常
        .accessDeniedHandler(this::notAuthException);         //非法访问异常

       // http.cors(); //设置不阻止跨域请求   (服务网关已设置，此处不再重复设置)
        http.csrf().disable();//禁用防csrf攻击
        http.headers().frameOptions().disable(); //禁用阻止iframe请求访问

        //引入JWT后不使用session追踪用户
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }



    @Bean  //配置一个跨域CorsConfigurationSource对象用于定义跨域的策略（配置）
    public CorsConfigurationSource corsConfigurationSource(){

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");  //允许任何主机资源发送跨域请求
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedHeader("*");

        corsConfiguration.setAllowCredentials(true); //允许客户端向服务器发送认证凭证 含有sessionId的cookie

        source.registerCorsConfiguration("/**",corsConfiguration); //什么地址应用这些配置


        return source;
    }



    public CustomFilter customFilter() throws Exception {

        CustomFilter filter = new CustomFilter();
        filter.setFilterProcessesUrl("/login");
        filter.setAuthenticationManager(this.authenticationManagerBean());//设置认证管理器
        filter.setUsernameParameter("u_id");
        filter.setPasswordParameter("u_pwd");
        filter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
        filter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
        return filter;
    }



    private void print(HttpServletResponse response,Result result) throws IOException {
                        ObjectMapper objectMapper = new ObjectMapper(); //这是一个工具对象，可以实现对象和json字符串互转
                        String jsonStr = objectMapper.writeValueAsString(result);
                        response.setContentType("application/json;charset=UTF-8");
                        PrintWriter writer = response.getWriter();//字符输出流，向浏览器输出
                        writer.print(jsonStr);
                        writer.flush();
                        writer.close();
    }
    private void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        CurrentUser currentUser = (CurrentUser) authentication.getPrincipal(); //当前用户
        TokenUser tokenUser = new TokenUser(currentUser.getUserId(),currentUser.getUserName());

        String token = TokenUtils.sign(tokenUser,currentUser.getPassword());


        Result result = Result.OK("登录成功",token);
        print(response,result);
    }
    private void onAuthenticationFailure(HttpServletRequest request,
                                         HttpServletResponse response,
                                         AuthenticationException exception) throws IOException, ServletException {
        Result result = Result.err(Result.CODE_ERR_BUSINESS,"账号或密码错误");
        print(response,result);
    }

    private void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //退出成功时删除缓存中的令牌
        TokenUtils.removeToken(request.getHeader(OaConstants.HEADER_NAME_TOKEN));
        Result result = Result.OK("退出成功");
        print(response,result);
    }
    private void onNotAuthException(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        //if ("XMLHttpRequest".equals(request.getHeader("X-Request-With"))){
            Result result = Result.err(Result.CODE_ERR_UNLOGINED,"请登陆后访问");
            print(response,result);
       // }else {
        //    response.sendRedirect(request.getContextPath()+"/login.html");
        //}
    }
    public void notAuthException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        
        Result result = Result.err(Result.CODE_ERR_BUSINESS,"权限不足");
        print(response,result);
    }

}
